using System;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Text;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Web;

using LongNet.Config;
using LongNet.Util;

namespace LongNet.Web
{
    /// <summary>
    /// A simple verify code image class. This class cannot be inherited.
    /// This class cannot be inherited.
    /// </summary>
    public static class VerifyImage
    {
        #region Private Fields

        private static VerifyImageElement config;

        #endregion

        #region Static Constructor

        static VerifyImage()
        {
            InitConfig(ConfigSectionHandler.Configuration.WebConfig.VerifyImage);
        }

        internal static void InitConfig(VerifyImageElement newConfig)
        {
            config = newConfig;
        }

        #endregion

        #region Public Static Methods

        /// <summary>
        /// Response the verify code image by using configuration file settings.
        /// </summary>
        /// <returns>The verify code text string.</returns>
        public static string ResponseImage()
        {
            return ResponseImage(config.Options);
        }

        /// <summary>
        /// Response the verify code image.
        /// </summary>
        /// <param name="options">The options for generating verify image.</param>
        /// <returns>The verify code text string.</returns>
        public static string ResponseImage(GenerateOptions options)
        {
            string verifyCodeString;

            var context = HttpContext.Current;
            byte[] data = new VerifyImageGenerator().GenerateImage(options, out verifyCodeString);

            if (context != null)
            {
                context.Response.ClearContent();
                context.Response.ContentType = "image/Jpeg";
                context.Response.BinaryWrite(data);
            }

            return verifyCodeString;
        }

        /// <summary>
        /// Generate the verify code image by using configuration file settings.
        /// </summary>
        /// <param name="verifyCodeString">The verify code text string.</param>
        /// <returns>A new byte array from the verify code image stream contents.</returns>
        public static byte[] GenerateImage(out string verifyCodeString)
        {
            return new VerifyImageGenerator().GenerateImage(config.Options, out verifyCodeString);
        }

        /// <summary>
        /// Generate the verify code image.
        /// </summary>
        /// <param name="options">The options for generating verify image.</param>
        /// <param name="verifyCodeString">The verify code text string.</param>
        /// <returns>A new byte array from the verify code image stream contents.</returns>
        public static byte[] GenerateImage(GenerateOptions options, out string verifyCodeString)
        {
            return new VerifyImageGenerator().GenerateImage(options, out verifyCodeString);
        }

        #endregion

        #region Nested Class

        /// <summary>
        /// Specifies the generate options for generating verify images.
        /// </summary>
        [Flags]
        public enum GenerateOptions
        {
            /// <summary>
            /// Use decimal numbers in verify text.
            /// </summary>
            Number = 0x01,

            /// <summary>
            /// Use alphabetic letters in verify text.
            /// </summary>
            Letter = 0x02,

            /// <summary>
            /// Use background for the generated verify image.
            /// </summary>
            Background = 0x04
        }

        /// <summary>
        /// This is an entity class to be used in case background for <see cref="VerifyImageGenerator"/>.
        /// </summary>
        private class BackgroundGroup
        {
            /// <summary>
            /// Gets or sets the group name.
            /// </summary>
            public string GroupName { get; set; }

            /// <summary>
            /// Gets or sets the background image.
            /// </summary>
            public Image BackgroundImage { get; set; }

            /// <summary>
            /// Gets or sets the font color list.
            /// </summary>
            public List<Color> FontColorList { get; set; }

            /// <summary>
            /// Initializes the BackgroundGroup object.
            /// </summary>
            public BackgroundGroup()
                : this(string.Empty, null, null)
            {
            }

            /// <summary>
            /// Initializes the BackgroundGroup object from the provided data.
            /// </summary>
            /// <param name="groupName">The group name.</param>
            /// <param name="backgroundImage">The background image.</param>
            /// <param name="fontColorList">The font color list.</param>
            public BackgroundGroup(string groupName, Image backgroundImage, List<Color> fontColorList)
            {
                this.GroupName = groupName;
                this.BackgroundImage = backgroundImage;
                this.FontColorList = fontColorList;
            }
        }

        /// <summary>
        /// This is a helper class to generate verify images.
        /// </summary>
        private class VerifyImageGenerator
        {
            #region Constants

            private const double PI = 3.1415926535897932384626433832795;
            private const string VerifyNumbers = "0123456789";
            private const string VerifyLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
            private const string VerifyNumbersAndLetters = "23456789abcdefghjklmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; // 去掉数字 1、0 字母 i、I、o、O
            private const string VerifyTextFontNames = "华文细黑|华文新魏|方正舒体|Arial|Basemic Symbol|Corbel|Georgia|Latha|Narkisim";

            #endregion

            #region Fields

            private static readonly List<Color> fontColorList;
            private static readonly List<FontFamily> fontFamilyList;
            private static readonly List<BackgroundGroup> backgroundGroupList;

            private Random random = new Random();

            #endregion

            #region Static Constructor

            static VerifyImageGenerator()
            {
                #region init fontColorList

                fontColorList = new List<Color>
                {
                    Color.FromArgb(215, 65, 0)/*棕*/, Color.FromArgb(80, 130, 30)/*绿*/, Color.FromArgb(25, 95, 135)/*蓝*/, Color.FromArgb(255, 153, 0)/*橙*/
                };

                #endregion

                #region init fontFamilyList

                fontFamilyList = new List<FontFamily>();
                string[] nameArray = VerifyTextFontNames.Split('|');
                foreach (string name in nameArray)
                {
                    if (name.IsNullOrEmpty() == false)
                    {
                        try
                        {
                            FontFamily fontFamily = new FontFamily(name);
                            fontFamilyList.Add(fontFamily);
                        }
                        catch
                        {
                            continue; // 字体不存在则继续下一个
                        }
                    }
                }
                if (fontFamilyList.Count == 0)
                {
                    throw new Exception(string.Format("Your OS does not support the font families : {0}", VerifyTextFontNames));
                }

                #endregion

                #region init backgroundGroupList

                backgroundGroupList = new List<BackgroundGroup>
                {
                    // Blue
                    new BackgroundGroup
                    {
                        GroupName = "Blue",
                        FontColorList = new List<Color>
                        {
                            Color.White, Color.FromArgb(255, 255, 0)/*黄*/, Color.FromArgb(255, 102, 0)/*红*/
                        }
                    },
                    // Green
                    new BackgroundGroup
                    {
                        GroupName = "Green",
                        FontColorList = new List<Color>
                        {
                            Color.White, Color.FromArgb(255, 255, 0)/*黄*/, Color.FromArgb(255, 102, 0)/*红*/
                        }
                    },
                    // Brown
                    new BackgroundGroup
                    {
                        GroupName = "Brown",
                        FontColorList = new List<Color>
                        {
                            Color.White, Color.FromArgb(255, 255, 0)/*黄*/, Color.FromArgb(153, 204, 0)/*绿*/
                        }
                    }
                };

                #endregion

                #region load background images from the assembly

                for (int i = 0, l = backgroundGroupList.Count; i < l; ++i)
                {
                    try
                    {
                        string fileName = string.Format("VerifyImage.{0}.jpg", backgroundGroupList[i].GroupName);
                        backgroundGroupList[i].BackgroundImage = Image.FromStream(Constants.ResourceHelper.GetStreamByFileName(fileName));
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }

                #endregion
            }

            #endregion

            #region Private Methods

            private int Next(int max)
            {
                return random.Next(max);
            }

            private int Next(int min, int max)
            {
                return random.Next(min, max);
            }

            private Color NextColor()
            {
                return NextColor(true);
            }

            private Color NextColor(bool useMyList)
            {
                if (useMyList)
                    return fontColorList[Next(0, fontColorList.Count)];
                else
                    return Color.FromArgb(Next(0, 255), Next(0, 255), Next(0, 255));
            }

            private Color NextColor(BackgroundGroup group)
            {
                return group.FontColorList[Next(0, group.FontColorList.Count)];
            }

            private FontFamily NextFontFamily()
            {
                return fontFamilyList[Next(0, fontFamilyList.Count)];
            }

            private FontStyle NextFontStyle(GenerateOptions options, int i)
            {
                if ((options & GenerateOptions.Letter) != 0)
                    return FontStyle.Regular;

                FontStyle italic = FontStyle.Regular;
                FontStyle underLine = FontStyle.Regular;
                FontStyle strikeout = FontStyle.Regular;

                if ((Next(1, 10) * i % 2) == 0)
                    italic = FontStyle.Italic;

                if ((Next(1, 256) * i % 10) == 0)
                    underLine = FontStyle.Underline;

                if ((Next(1, 256) * i % 20) == 0)
                    strikeout = FontStyle.Strikeout;

                FontStyle ret = FontStyle.Regular;
                if (italic == FontStyle.Italic)
                {
                    ret = italic;
                }
                if (underLine == FontStyle.Underline)
                {
                    if (ret == FontStyle.Regular)
                        ret = underLine;
                    else
                        ret |= underLine;
                }
                if (strikeout == FontStyle.Strikeout)
                {
                    if (ret == FontStyle.Regular)
                        ret = strikeout;
                    else
                        ret |= strikeout;
                }

                return ret;
            }

            private BackgroundGroup NextBackgroundGroup()
            {
                return backgroundGroupList[Next(0, backgroundGroupList.Count)];
            }

            private string GenerateVerifyCodeString(GenerateOptions options)
            {
                char[] chs = new char[4];
                string source = VerifyNumbersAndLetters;

                bool useNumber = (options & GenerateOptions.Number) != 0;
                bool useLetter = (options & GenerateOptions.Letter) != 0;

                if (useNumber && useLetter)
                    source = VerifyNumbersAndLetters;
                else if (useNumber)
                    source = VerifyNumbers;
                else if (useLetter)
                    source = VerifyLetters;
                else
                {
                    source = VerifyLetters;
                    //throw new NotSupportedException("Not supported GenerateOptions.");
                }

                for (int i = 0, l = chs.Length; i < l; ++i)
                {
                    do { chs[i] = source[Next(0, source.Length)]; } while (i > 0 && chs[i] == chs[i - 1]);
                }

                return new string(chs);
            }

            private Bitmap TwistImage(bool withBackground, Bitmap srcBmp, bool xDir, double multValue, double phase)
            {
                // xDir 如果扭曲则选择为True
                // multValue 波形的幅度倍数，越大扭曲的程度越高，一般为3
                // phase 波形的起始相位，取值区间 [0-2*PI)

                // 因为扭曲图像比较消耗性能，暂时不用
                bool bTwist = false;
                if (!bTwist)
                    return srcBmp;

                Bitmap destBmp = new Bitmap(srcBmp.Width, srcBmp.Height);

                if (withBackground == false)
                {
                    using (Graphics graphics = Graphics.FromImage(destBmp))
                    {
                        graphics.Clear(Color.White);
                    }
                }

                double baseAxisLen = xDir ? (double)destBmp.Height : (double)destBmp.Width;
                for (int i = 0; i < destBmp.Width; i++)
                {
                    for (int j = 0; j < destBmp.Height; j++)
                    {
                        double dx = 0;
                        dx = xDir ? (PI * 2 * (double)j) / baseAxisLen : (PI * 2 * (double)i) / baseAxisLen;
                        dx += phase;

                        double dy = Math.Sin(dx);
                        // 取得当前点的颜色
                        int oldX = 0, oldY = 0;
                        oldX = xDir ? i + (int)(dy * multValue) : i;
                        oldY = xDir ? j : j + (int)(dy * multValue);
                        System.Drawing.Color color = srcBmp.GetPixel(i, j);
                        if (oldX >= 0 && oldX < destBmp.Width && oldY >= 0 && oldY < destBmp.Height)
                        {
                            destBmp.SetPixel(oldX, oldY, color);
                        }
                    }
                }

                return destBmp;
            }

            #endregion

            #region Public Methods

            /// <summary>
            /// Generate the verify code image.
            /// </summary>
            /// <param name="options">The options for generating verify image.</param>
            /// <param name="verifyCodeString">The verify code text string.</param>
            /// <returns>A new byte array from the verify code image stream contents.</returns>
            public byte[] GenerateImage(GenerateOptions options, out string verifyCodeString)
            {
                verifyCodeString = GenerateVerifyCodeString(options);

                bool withBackground = (options & GenerateOptions.Background) != 0;

                using (Bitmap imgText = new Bitmap(130, 53))
                {
                    using (Graphics gText = Graphics.FromImage(imgText))
                    {
                        gText.SmoothingMode = SmoothingMode.AntiAlias;
                        gText.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;

                        #region 提取颜色

                        Color lineColor;
                        Color[] textColor = new Color[2];
                        BackgroundGroup group = NextBackgroundGroup();
                        if (withBackground == false)
                        {
                            gText.Clear(Color.White);
                            textColor[0] = NextColor();
                            do { textColor[1] = NextColor(); } while (textColor[1] == textColor[0]);
                        }
                        else
                        {
                            textColor[0] = NextColor(group);
                            do { textColor[1] = NextColor(group); } while (textColor[1] == textColor[0]);
                        }
                        lineColor = textColor[Next(100) % 2];

                        #endregion

                        #region 绘画字体

                        FontFamily fontFamily = NextFontFamily();
                        for (int i = 0, l = verifyCodeString.Length; i < l; ++i)
                        {
                            char c = verifyCodeString[i];
                            Brush brush = new SolidBrush(textColor[i < 2 ? 0 : 1]);
                            Font font = new Font(fontFamily, Next(30, 35), NextFontStyle(options, i));

                            int x = i * 25;
                            int y = 0;
                            // 错位处理
                            int z = (options & GenerateOptions.Letter) != 0 ? Next(4, 8) : Next(7, 9);
                            x += (i % 2 == 1) ? 0 : z;
                            y = Next(-2, 3);

                            gText.DrawString(c.ToString(), font, brush, x, y);
                        }

                        #endregion

                        #region 噪音曲线

                        Pen linePen = new Pen(lineColor, Next(2, 4));
                        gText.DrawEllipse(linePen, Next(5, 25), Next(15, 35), Next(150, 200), Next(60, 100));

                        #endregion

                        #region 生成图片

                        using (Bitmap imgTwist = TwistImage(withBackground, imgText, true, Next(4, 7), Next(3, 6)))
                        {
                            if (withBackground == false)
                            {
                                using (MemoryStream ms = new MemoryStream())
                                {
                                    var ici = ImageUtils.GetImageCodecInfo(".jpg");
                                    if (ici == null)
                                        imgTwist.Save(ms, ImageFormat.Jpeg);
                                    else
                                        imgTwist.Save(ms, ici, ImageUtils.GetQualityEncoderParameters(100));

                                    byte[] bytes = ms.ToArray();
                                    ms.Close();
                                    imgTwist.Dispose();
                                    gText.Dispose();
                                    imgText.Dispose();

                                    return bytes;
                                }
                            }
                            else
                            {
                                using (Bitmap imgBackground = new Bitmap(group.BackgroundImage))
                                {
                                    using (Graphics gBackground = Graphics.FromImage(imgBackground))
                                    {
                                        gBackground.SmoothingMode = SmoothingMode.AntiAlias;
                                        gBackground.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
                                        gBackground.DrawImage(imgTwist, new Point(0, 0));

                                        using (MemoryStream ms = new MemoryStream())
                                        {
                                            var ici = ImageUtils.GetImageCodecInfo(".jpg");
                                            if (ici == null)
                                                imgBackground.Save(ms, ImageFormat.Jpeg);
                                            else
                                                imgBackground.Save(ms, ici, ImageUtils.GetQualityEncoderParameters(100));

                                            byte[] bytes = ms.ToArray();
                                            ms.Close();
                                            gBackground.Dispose();
                                            imgBackground.Dispose();
                                            gText.Dispose();
                                            imgText.Dispose();

                                            return bytes;
                                        }
                                    }
                                }
                            }
                        }

                        #endregion
                    }
                }
            }

            #endregion
        }

        #endregion
    }
}
